package com.shm.component;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.entity.Chat;
import com.ruoyi.common.exception.GlobalException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.shm.component.delay.DelayQueueManager;
import com.shm.component.delay.DelayTask;
import com.shm.constant.RabbitMqConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/websocket/{username}")
@Component//将WebSocketServer注册为spring的一个bean
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 记录当前在线连接的客户端的session
     */
    public static final Map<String, Session> usernameAndSessionMap = new ConcurrentHashMap<>();
    /**
     * 记录正在进行的聊天的发出者和接收者
     */
    public static final Map<String, Integer> fromToMap = new ConcurrentHashMap<>();
    /**
     * 用户Session保留时间，如果超过该时间，用户还没有给服务端发送消息，认为用户下线，删除其Session
     * 注意：该时间需要比客户端的心跳时间更长
     */
    private static final long expire = 70 * 1000;

    // websocket为多例模式，无法直接注入，需要换成下面的方式
//    @Autowired
//    RabbitTemplate rabbitTemplate;

    private static RabbitTemplate rabbitTemplate;

    @Autowired
    public void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
        WebSocketServer.rabbitTemplate = rabbitTemplate;
    }

    @Autowired
    private static DelayQueueManager delayQueueManager;

    @Autowired
    public void setDelayQueueManager(DelayQueueManager delayQueueManager) {
        WebSocketServer.delayQueueManager = delayQueueManager;
    }

    /**
     * 浏览器和服务端连接建立成功之后会调用这个方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        usernameAndSessionMap.put(username, session);
        // 建立延时任务，如果到expire时间，客户端还是没有和服务器有任何交互的话，就删除该用户的session，表示该用户下线
        delayQueueManager.put(new DelayTask(username, expire));
        log.info("有新用户加入，username={}, 当前在线人数为：{}", username, usernameAndSessionMap.size());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        usernameAndSessionMap.remove(username);
        log.info("有一连接关闭，移除username={}的用户session, 当前在线人数为：{}", username, usernameAndSessionMap.size());
    }

    /**
     * 发生错误的时候会调用这个方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    public void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }


    /**
     * onMessage方法是一个消息的中转站
     * 1、首先接受浏览器端socket.send发送过来的json数据
     * 2、然后解析其数据，找到消息要发送给谁
     * 3、最后将数据发送给相应的人
     *
     * @param message 客户端发送过来的消息 数据格式：{"from":"user1","to":"admin","text":"你好呀"}
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("username") String username) {
        if (StringUtils.isEmpty(username)) {
            throw new GlobalException("消息发送失败，发送方为空");
        }
        log.info("服务端接收到 {} 的消息，消息内容是:{}", username, message);

        // 收到用户的信息，删除之前的延时任务，创建新的延时任务
        delayQueueManager.put(new DelayTask(username, expire));
        if (!usernameAndSessionMap.containsKey(username)) {
            // 可能用户挂机了一段时间，被下线了，后面又重新回来发信息了，需要重新将用户和session添加字典中
            usernameAndSessionMap.put(username, session);
        }

        // 将json字符串转化为json对象
        JSONObject obj = JSON.parseObject(message);
        String status = (String) obj.get("status");
        // 获取消息的内容
        String text = (String) obj.get("text");
        // 查看消息要发送给哪个用户
        String to = (String) obj.get("to");

        if (!("ping".equals(status) || "start".equals(status) || "end".equals(status))) {
            // --if-- 如果不是心跳检测消息，说明是私聊消息，需要校验内容和接收方
            if (StringUtils.isEmpty(to)) {
                throw new GlobalException("消息发送失败，接收方为空");
            }
            if (StringUtils.isEmpty(text)) {
                throw new GlobalException("消息发送失败，消息为空");
            }
        }

        String fromToKey = username + "-" + to;
        String toFromKey = to + "-" + username;
        if (status != null) {
            if (status.equals("start")) {
                fromToMap.put(fromToKey, 1);
            } else if (status.equals("end")) {
                System.out.println("移除销毁的fromToKey:" + fromToKey);
                fromToMap.remove(fromToKey);
            }
        } else {
            // 封装数据发送给消息队列
            Chat chat = new Chat();
            chat.setFromWho(username);
            chat.setToWho(to);
            chat.setContent(text);
            chat.setIsRead(0);

            // 根据to来获取相应的session，然后通过session将消息内容转发给相应的用户
            Session toSession = usernameAndSessionMap.get(to);
            if (toSession != null) {
                JSONObject jsonObject = new JSONObject();
                // 设置消息来源的用户名
                jsonObject.put("from", username);
                // 设置消息内容
                jsonObject.put("text", text);
                log.info("发送消息给用户 {} ，session {}，消息内容是：{} ", to, toSession, jsonObject.toString());
                // 服务端发送消息给目标客户端
                this.sendMessage(jsonObject.toString(), toSession);
                if (fromToMap.containsKey(toFromKey)) {
                    // --if-- 如果接收方现在正在聊天，认为其读了消息，将消息置为已读状态
                    chat.setIsRead(1);
                }
            } else {
                log.info("发送失败，未找到用户 {} 的session", to);
            }

            // 将消息任务推送给消息队列
            rabbitTemplate.convertAndSend(RabbitMqConstant.CHAT_STORAGE_EXCHANGE, RabbitMqConstant.CHAT_STORAGE_ROUTER_KEY, chat);
        }
    }

}